####################################################################### # Function to on the fly compile by c# code # Reference: http://blogs.msdn.com/powershell/archive/2006/04/25/583236.aspx ####################################################################### function Compile-Csharp ([string] $code, $FrameworkVersion="v2.0.50727", [Array]$References) { # # Get an instance of the CSharp code provider # $cp = new-object Microsoft.CSharp.CSharpCodeProvider # # Build up a compiler params object... $framework = Join-Path $env:windir "Microsoft.NET\Framework\$FrameWorkVersion" $refs = New-Object Collections.ArrayList $refs.AddRange( @("${framework}\System.dll", "${framework}\system.windows.forms.dll", "${framework}\System.data.dll", "${framework}\System.Drawing.dll", "${framework}\System.Xml.dll")) if ($references.Count -ge 1) { $refs.AddRange($References) } $cpar = New-Object System.CodeDom.Compiler.CompilerParameters $cpar.GenerateInMemory = $true $cpar.GenerateExecutable = $false $cpar.OutputAssembly = "custom" $cpar.ReferencedAssemblies.AddRange($refs) $cr = $cp.CompileAssemblyFromSource($cpar, $code) if ( $cr.Errors.Count) { $codeLines = $code.Split("`n"); foreach ($ce in $cr.Errors) { write-host "Error: $($codeLines[$($ce.Line - 1)])" $ce |out-default } Throw "INVALID DATA: Errors encountered while compiling code" } } # Check for verbose param if($Args -contains "-verbose" -or $Args -contains "-vb") { $VerbosePreference = "Continue"; } # load required .NET Assemblies $temp = [System.Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices") # C# code for doing site link calculations $siteCostCode = @' using System; using System.Collections; using System.Runtime.InteropServices; using System.Text; namespace SiteCost { public class SiteCostLib { [StructLayout(LayoutKind.Sequential)] internal struct DS_SITE_COST_INFO { public int errorCode; public int cost; } [DllImport("Ntdsapi", EntryPoint = "DsBindToISTGW", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] internal static extern int DsBindingToISTG(string siteName, out IntPtr hDS); [DllImport("Ntdsapi", EntryPoint = "DsQuerySitesByCostW", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] internal static extern int DsQuerySitesByCost(IntPtr hDS, string fromSite, string[] toSites, int cToSites, int flags, out IntPtr pCostInfo); [DllImport("Ntdsapi", EntryPoint = "DsQuerySitesFree", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] internal static extern void DsQuerySitesFree(IntPtr pCostInfo); internal static int[] UnMarshalResults(IntPtr pSiteInfo, int cSiteInfo) { int[] resultArray = new int[cSiteInfo]; IntPtr pCurrentResult = pSiteInfo; for (int x = 0; x < cSiteInfo; x++) { DS_SITE_COST_INFO result = (DS_SITE_COST_INFO) Marshal.PtrToStructure(pCurrentResult, typeof(DS_SITE_COST_INFO)); if (result.errorCode == 0) { resultArray[x] = result.cost; } else { resultArray[x] = -1; } // Move to the next result pCurrentResult = new IntPtr(pCurrentResult.ToInt64() + Marshal.SizeOf(result)); } return resultArray; } public static int QuerySitesByCost(string fromSite, string[] toSites, out int[] costInfo) { int status = 0; IntPtr hDS = IntPtr.Zero; IntPtr pCostInfo = IntPtr.Zero; status = DsBindingToISTG( null, out hDS ); if (status != 0) { costInfo = null; return status; } else { status = DsQuerySitesByCost( hDS, fromSite, toSites, toSites.Length, 0, out pCostInfo ); if (status != 0) { costInfo = null; return status; } else { // Unmarshal the results costInfo = UnMarshalResults(pCostInfo, toSites.Length); // Free the site list DsQuerySitesFree(pCostInfo); return status; } } } } } '@ # Compile our C# code for doing the site cost calculations Compile-CSharp $siteCostCode # Get current forest info $forest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest(); # Build list of sites that have Exchange Servers [string[]]$exchangeSites = @(); Get-ExchangeServer | Where {$_.IsClientAccessServer} | foreach { $exchangeSites += $_.Site.Name; } # or you can manually set the Exchange sites if you prefer # $exchangeSites = "Charlotte","Redmond" # Loop through each Exchange Site foreach($clientsite in $forest.sites) { $leastCostExchangeName = ""; $leastCostExchangeCost = 99999; [int[]] $costArray = $null; $siteCostStatus = [SiteCost.SiteCostLib]::QuerySitesByCost($clientsite.Name, $exchangeSites, [ref]$costArray); # find the lowest cost and note the site for($i=0; $i -lt $costArray.Length; $i++) { if($costArray[$i] -lt $leastCostExchangeCost) { $leastCostExchangeCost = $costArray[$i]; $leastCostExchangeName = $exchangeSites[$i]; } } write-host ("Least cost Exchange Site for client site " + $clientsite.Name + " is " + $leastCostExchangeName + " Cost: " +$leastCostExchangeCost); # Get the list of $closestCASList = Get-ExchangeServer | Where {$_.IsClientAccessServer} | where {$_.site.Name -eq $leastCostExchangeName}; foreach($closeCas in $closestCASList) { # Get existing attribute $clientsiteScopeList = (Get-ClientAccessServer -identity $closeCas.Name).AutoDiscoverSiteScope; # big special case for $null if($clientsiteScopeList -eq $null) { Set-ClientAccessServer -identity $closeCas.Name -AutoDiscoverSiteScope ""; $clientsiteScopeList = (Get-ClientAccessServer -identity $closeCas.Name).AutoDiscoverSiteScope; $clientsiteScopeList.Clear(); } # If It's not already in the list, add it. if($clientsiteScopeList -notcontains $clientsite.Name) { $clientsiteScopeList.Add($clientsite.Name); } # Set the updated list back to using Set-ClientAccessServer Set-ClientAccessServer -identity $closeCas.Name -AutodiscoverSiteScope $clientsiteScopeList } }